// 版权归Go作者所有。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// Package testenv提供了关于Go团队运行的不同测试环境中有哪些功能的信息。
// 
// 这是一个内部包，因为这些细节是Go团队测试设置（在build.golang.org上）的特定
// 而不是一般测试的基础。
package testenv

import (
	"errors"
	"flag"
	"internal/cfg"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"testing"
)

// Builder报告运行此测试的Builder的名称
// （例如，“linux-amd64”或“windows-386-gce”）。
// 如果测试未在构建基础结构上运行，
// 构建器将返回空字符串。
func Builder() string {
	return os.Getenv("GO_BUILDER_NAME")
}

// HasGoBuild报告当前系统是否可以使用“go build”生成程序
// 然后使用os.StartProcess或exec.Command运行程序。
func HasGoBuild() bool {
	if os.Getenv("GO_GCFLAGS") != "" {
		// 要求go命令的每个调用程序
		// 传递“-gcflags=“+os.Getenv”（“go_gcflags”）太多了。
		// 目前，如果设置了$GO_GCFLAGS，请报告我们无法运行GO构建。
		return false
	}
	switch runtime.GOOS {
	case "android", "js", "ios":
		return false
	}
	return true
}

// 必须检查当前系统是否可以使用“go build”生成程序
// 然后使用os.StartProcess或exec.Command运行程序。
// 如果没有，则必须有GOBUILD调用t.Skip并给出解释。
func MustHaveGoBuild(t testing.TB) {
	if os.Getenv("GO_GCFLAGS") != "" {
		t.Skipf("skipping test: 'go build' not compatible with setting $GO_GCFLAGS")
	}
	if !HasGoBuild() {
		t.Skipf("skipping test: 'go build' not available on %s/%s", runtime.GOOS, runtime.GOARCH)
	}
}

// HasGoRun报告当前系统是否可以运行带有“go run”的程序。
func HasGoRun() bool {
	// 目前，go run和go build是相同的。
	return HasGoBuild()
}

// MustHaveGoRun检查当前系统是否可以运行带有“go run”的程序。
// 如果不能，MustHaveGoRun将调用t.Skip并给出解释。
func MustHaveGoRun(t testing.TB) {
	if !HasGoRun() {
		t.Skipf("skipping test: 'go run' not available on %s/%s", runtime.GOOS, runtime.GOARCH)
	}
}

// GoToolPath报告Go工具的路径。
// 它是GoTool的一个方便的包装器。
// 如果工具不可用，GoToolPath将调用t.Skip。
// 如果该工具应该可用，但不可用，则GoToolPath调用t.Fatal。
func GoToolPath(t testing.TB) string {
	MustHaveGoBuild(t)
	path, err := GoTool()
	if err != nil {
		t.Fatal(err)
	}
	// 添加影响Go命令的所有环境变量以测试元数据。
	// 当这些变量更改时，缓存的测试结果将无效。
	// 见golang.org/issue/32285。
	for _, envVar := range strings.Fields(cfg.KnownEnv) {
		os.Getenv(envVar)
	}
	return path
}

// GoTool报告Go工具的路径。
func GoTool() (string, error) {
	if !HasGoBuild() {
		return "", errors.New("platform cannot run go tool")
	}
	var exeSuffix string
	if runtime.GOOS == "windows" {
		exeSuffix = ".exe"
	}
	path := filepath.Join(runtime.GOROOT(), "bin", "go"+exeSuffix)
	if _, err := os.Stat(path); err == nil {
		return path, nil
	}
	goBin, err := exec.LookPath("go" + exeSuffix)
	if err != nil {
		return "", errors.New("cannot find go tool: " + err.Error())
	}
	return goBin, nil
}

// HasExec报告当前系统是否可以使用os.StartProcess或（更常见的）exec.Command启动新进程。
func HasExec() bool {
	switch runtime.GOOS {
	case "js", "ios":
		return false
	}
	return true
}

// HasSrc报告整个源代码树是否在GOROOT下可用。
func HasSrc() bool {
	switch runtime.GOOS {
	case "ios":
		return false
	}
	return true
}

// MustHaveExec检查当前系统是否可以使用os.StartProcess或（更常见的）exec.Command启动新进程。
// 如果没有，则必须让exec调用t.Skip并给出解释。
func MustHaveExec(t testing.TB) {
	if !HasExec() {
		t.Skipf("skipping test: cannot exec subprocess on %s/%s", runtime.GOOS, runtime.GOARCH)
	}
}

var execPaths sync.Map // path->error 

// MustHaveExecPath检查当前系统是否可以使用os.StartProcess或（更常见的）exec.Command启动命名的可执行文件
// 。
// 如果没有，则必须有execpath调用t.Skip并给出解释。
func MustHaveExecPath(t testing.TB, path string) {
	MustHaveExec(t)

	err, found := execPaths.Load(path)
	if !found {
		_, err = exec.LookPath(path)
		err, _ = execPaths.LoadOrStore(path, err)
	}
	if err != nil {
		t.Skipf("skipping test: %s: %s", path, err)
	}
}

// HasExternalNetwork报告当前系统是否可以使用
// 外部（非本地主机）网络。
func HasExternalNetwork() bool {
	return !testing.Short() && runtime.GOOS != "js"
}

// 必须进行外部网络检查，确保当前系统可以使用
// 外部（非本地主机）网络。
// 如果没有，则必须有外部网络调用t。跳过并给出解释。
func MustHaveExternalNetwork(t testing.TB) {
	if runtime.GOOS == "js" {
		t.Skipf("skipping test: no external network on %s", runtime.GOOS)
	}
	if testing.Short() {
		t.Skipf("skipping test: no external network in -short mode")
	}
}

var haveCGO bool

// HasCGO报告当前系统是否可以使用cgo。
func HasCGO() bool {
	return haveCGO
}

// 必须有cgo调用t。如果cgo不可用，则跳过。
func MustHaveCGO(t testing.TB) {
	if !haveCGO {
		t.Skipf("skipping test: no cgo")
	}
}

// CANInternallLink报告当前系统是否可以将程序链接到
// 内部链接。
// （这与cmd/internal/sys.MustLinkExternal相反。保持它们同步。）
func CanInternalLink() bool {
	switch runtime.GOOS {
	case "android":
		if runtime.GOARCH != "arm64" {
			return false
		}
	case "ios":
		if runtime.GOARCH == "arm64" {
			return false
		}
	}
	return true
}

// /MustInternalLink检查当前系统是否可以将程序与内部
// 链接。
// 如果不是，则MustInternalLink调用t.Skip并给出解释。
func MustInternalLink(t testing.TB) {
	if !CanInternalLink() {
		t.Skipf("skipping test: internal linking on %s/%s is not supported", runtime.GOOS, runtime.GOARCH)
	}
}

// HasSymlink报告当前系统是否可以使用os.Symlink。
func HasSymlink() bool {
	ok, _ := hasSymlink()
	return ok
}

// MustHaveSymlink报告当前系统是否可以使用os.Symlink。
// 如果没有，则必须有符号链接调用t.Skip并给出解释。
func MustHaveSymlink(t testing.TB) {
	ok, reason := hasSymlink()
	if !ok {
		t.Skipf("skipping test: cannot make symlinks on %s/%s%s", runtime.GOOS, runtime.GOARCH, reason)
	}
}

// HasLink报告当前系统是否可以使用os.Link。
func HasLink() bool {
	// 从Android版本M（棉花糖）开始，硬链接文件被阻止
	// 尝试调用文件上的link（）将返回EACCES。
	// -https:
	return runtime.GOOS != "plan9" && runtime.GOOS != "android"
}

// 必须有Link报告当前系统是否可以使用os.Link。
// 如果没有，则必须调用t.Skip并给出解释。
func MustHaveLink(t testing.TB) {
	if !HasLink() {
		t.Skipf("skipping test: hardlinks are not supported on %s/%s", runtime.GOOS, runtime.GOARCH)
	}
}

var flaky = flag.Bool("flaky", false, "run known-flaky tests too")

func SkipFlaky(t testing.TB, issue int) {
	t.Helper()
	if !*flaky {
		t.Skipf("skipping known flaky test without the -flaky flag; see golang.org/issue/%d", issue)
	}
}

func SkipFlakyNet(t testing.TB) {
	t.Helper()
	if v, _ := strconv.ParseBool(os.Getenv("GO_BUILDER_FLAKY_NET")); v {
		t.Skip("skipping test on builder known to have frequent network failures")
	}
}

// CleanCmdEnv将使用环境填充cmd.Env，不包括某些
// 可以修改Go工具行为的变量，如
// GODEBUG和GOTRACEBACK。
func CleanCmdEnv(cmd *exec.Cmd) *exec.Cmd {
	if cmd.Env != nil {
		panic("environment already set")
	}
	for _, env := range os.Environ() {
		// 从环境中排除GODEBUG，以防止其输出
		// 破坏试图解析其他命令输出的测试。
		if strings.HasPrefix(env, "GODEBUG=") {
			continue
		}
		// 出于同样的原因排除GOTRACEBACK。
		if strings.HasPrefix(env, "GOTRACEBACK=") {
			continue
		}
		cmd.Env = append(cmd.Env, env)
	}
	return cmd
}

// CPUIsSlow报告运行测试的CPU是否怀疑运行缓慢。
func CPUIsSlow() bool {
	switch runtime.GOARCH {
	case "arm", "mips", "mipsle", "mips64", "mips64le":
		return true
	}
	return false
}

// 如果设置了-short并且运行测试的CPU是
// 怀疑速度慢，则SkipIfShortAndSlow跳过t。
// 
// （这对于CPU密集型测试非常有用，否则这些测试会很快完成。）
func SkipIfShortAndSlow(t testing.TB) {
	if testing.Short() && CPUIsSlow() {
		t.Helper()
		t.Skipf("skipping test in -short mode on %s", runtime.GOARCH)
	}
}
